/*

	UIObject
	---------

	@file 		UIObject.js
	@version 	$Revision: 22 $
	@date 		$Date: 2011-04-21 22:08:21 +0000 (Thu, 21 Apr 2011) $
	@author 	$Author: me@gideonfarrell.co.uk $

	Copyright (c) 2011 Gideon Farrell <http://www.gideonfarrell.co.uk>

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT
	HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED,
	INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR
	FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE
	OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS,
	COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.COPYRIGHT HOLDERS WILL NOT
	BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL
	DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://gnu.org/licenses/>.

*/

var UIObject = new Class({
	/**
	 * List of components
	*/
	__components:	{},
	
	/**
	 * List of elements
	*/
	__elements:		{},
	
	/**
	 * Settings object
	*/
	settings:		{},
	
	/**
	 * Whether or not to automatically structure the HTML DOM according to the structure of __elements
	*/
	autoStruct:		true,
	
	/**
	 * Whether or not to automatically render after initialisation
	*/
	autoRender:		true,
	
	/**
	 * initialize function.
	 * Class constructor, performs initialisation tasks before passing baton to user-defined callbacks.
	 *
	 * @final
	 * @access public
	 * @return void
	*/
	initialize: function () {
		this.__components = new Hash(this.__components);
		this.__elements = new Hash(this.__elements);
		if(this.autoStruct === true) {
			if(arguments.length > 0 && $type(arguments[0]) == "element") {
				this.addElement("root", (new Element("div")).inject(arguments[0], "before"));
				arguments[0].inject(this.element("root"));
			}
		}
		
		this.preInit();
		this.init.apply(this, $splat(arguments));
		this.postInit();
		
		if(this.autoRender === true) {
			this.__render();
		}
	},
	
	/**
	 * preInit callback.
	 * Called before initialisation.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	preInit: function () {}.protect(),
	
	/**
	 * init function.
	 * Effective constructor of the UIObject derived class.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	init: function (){}.protect(),
	
	/**
	 * postInit callback.
	 * Called after initialisation.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	postInit: function () {}.protect(),
	
	/**
	 * setDefaults function.
	 * Sets the default settings.
	 *
	 * @access public
	 * @param object settings
	 * @return void
	*/
	setDefaults: function (settings) {
		if($type(settings) == "object") {
			this.settings = $merge(this.settings, settings);
		}
	},
	
	/**
	 * addComponent function.
	 * Adds a component to the UIObject.
	 *
	 * @access public
	 * @param string name
	 * @param object component
	 * @return void
	*/
	addComponent: function (name, component) {
		this.__components[name] = component;
	},
	
	/**
	 * addComponents function.
	 * Adds multiple components to the UIObject.
	 *
	 * @access public
	 * @param object components
	 * @return void
	*/
	addComponents: function (components) {
		for(var name in components) {
			this.addComponent(name, components[name]);
		}
	},
	
	/**
	 * component function.
	 * Retrieves a named component.
	 *
	 * @access public
	 * @param string name
	 * @return object component
	*/
	component: function (name) {
		if(this.__components.has(name)) {
			return this.__components.get(name);
		} else {
			return null;
		}
	},
	
	/**
	 * __getElementParentNode function.
	 * Gets the parent node for a designated element.
	 *
	 * @access protected
	 * @param string locus
	 * @return object parent node
	*/
	__getElementParentNode: function (locus) {
		var loci = locus.split(".");
		if(loci.length == 1) {
			return (this.__elements.has(locus)) ? this.__elements : null;
		} else {
			var current = this.__elements;
			var iterate = 0;
			do {
				if(iterate == 0) {
					if(!$chk(current[loci[iterate]])) {
						return null;
					}
					current = current[loci[iterate]];
				} else {
					if(!$chk(current.children[loci[iterate]])) {
						return null;
					}
					current = current.children[loci[iterate]];
				}
				iterate++;
			} while(iterate < loci.length-1);
			
			return ($chk(current.children[loci[iterate]])) ? current : null;
		}
	}.protect(),
	
	/**
	 * __getElementNode function.
	 * Gets the node for a designated element in the __elements tree.
	 *
	 * @access protected
	 * @param string locus
	 * @return object element node
	*/
	__getElementNode: function (locus) {
		var loci = locus.split(".");
		var parent = this.__getElementParentNode(locus);
		
		if(loci.length == 1) {
			return (this.__elements.has(locus)) ? this.__elements.get(locus) : null;
		} else {		
			return ($chk(parent) && $chk(parent.children[loci.getLast()])) ? parent.children[loci.getLast()] : null;
		}
	}.protect(),
	
	/**
	 * addElement function.
	 * Adds an element to the UIObject.
	 * 
	 * @access public
	 * @param string locus
	 * @param HTMLElement element
	 * @param boolean autoStructure
	 * @return void
	*/
	addElement: function (locus, element, autoStructure) {
		if($type(element) == "element") {
			var loci = locus.split(".");
			var h = new Hash({
				element:	element,
				children:	{}
			});
			this.destroyElement(locus);
			if(loci.length == 1) {
				this.__elements[locus] = h;
				
				if(autoStructure === true && this.__elements.has("root")) {
					element.inject(this.element("root"));
				}
			} else {
				var current = this.__elements;
				var iterate = 0;
				do {
					if(iterate == 0) {
						if(!$chk(current[loci[iterate]])) {
							//Create a blank holder
							current[loci[iterate]] = new Hash({element:null, children:{}});
						}
						current = current[loci[iterate]];
					} else {
						if(!$chk(current.children[loci[iterate]])) {
							//Create a blank holder
							current.children[loci[iterate]] = new Hash({element:null, children:{}});
						}
						current = current.children[loci[iterate]];
					}
					iterate++;
				} while(iterate < loci.length-1);				
				current.children[loci.getLast()] = h;
				
				if(autoStructure === true) {
					element.inject(current.get("element"));
				}
			}
		}
	},
	
	/**
	 * destroyElement function.
	 * Destroys a designated element.
	 *
	 * @access public
	 * @param string locus
	 * @return void
	*/
	destroyElement: function (locus) {
		if($chk(this.element(locus))) {
			this.element(locus).destroy();
			var loci = locus.split(".");
			if(loci.length == 1) {
				delete this.__elements[locus];
			} else {
				delete this.__getElementParentNode(locus).children[locus.split(".").getLast()];
			}
		}
	},
	
	/**
	 * addElements function.
	 * Adds multiple elements to the UIObject in one go.
	 *
	 * @access public
	 * @param object elements
	 * @return false
	*/
	addElements: function (elements) {
		for(var locus in elements) {
			this.addElement(locus, elements[locus]);
		}
	},
	
	/**
	 * element function
	 * Retrieves a designated element.
	 * 
	 * @access public
	 * @param string locus
	 * @return HTMLElement element
	*/
	element: function (locus) {
		var loci = locus.split(".");
		if(loci.length == 1) {
			return (this.__elements.has(locus)) ? this.__elements.get(locus).get("element") : null;
		} else {
			var current = this.__elements;
			var iterate = 0;
			do {
				if(iterate == 0) {
					if(!$chk(current[loci[iterate]])) {
						return null;
					}
					current = current[loci[iterate]];
				} else {
					if(!$chk(current.children[loci[iterate]])) {
						return null;
					}
					current = current.children[loci[iterate]];
				}
				iterate++;
			} while(iterate < loci.length-1);
			
			return ($chk(current.children[loci[iterate]])) ? current.children[loci[iterate]].element : null;
		}
	},
	
	/**
	 * elements function.
	 * Retrieves a group of elements as specified.
	 *
	 * @access public
	 * @param string root
	 * @param boolean include_root
	 * @return ElementList elements
	*/
	elements: function (root, include_root) {
		var parent = ($chk(root)) ? this.__getElementNode(root).children : this.__elements;
		
		var prefix = ($chk(root)) ? root+"." : "";
		
		var els = [];
		
		if($chk(include_root) && include_root === true) {
			els.push(this.element(root));
		}
		
		$each(parent, function(item, locus) {
			if($type(item.element) == "element") {
				els.push(item.element);
			}
			if($type(item.children) == "object") {
				els.combine(this.elements(prefix+locus));
			}
		}, this);
		
		return (new Elements(els));
	},
	
	/**
	 * preRender callback.
	 * Called just before rendering occurs.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	preRender: function () {}.protect(),
	
	/**
	 * render function.
	 * Renders the interface object for the user.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	render: function () {}.protect(),
	
	/**
	 * postRender callback.
	 * Called just after rendering occurs.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	postRender: function () {}.protect(),
	
	/**
	 * __render function.
	 * A wrapper for controlling the flow of the render process..
	 *
	 * @access protected
	 * @return void
	*/
	__render: function () {
		this.preRender();
		this.render();
		this.postRender();
	}.protect(),
	
	/**
	 * parseSettingsFromElement function.
	 * Retrieves a JSON settings string from an element and reads it into __settings.
	 *
	 * @access protected
	 * @param HTMLElement element
	 * @param string attribute
	 * @return void
	*/
	parseSettingsFromElement: function (element, attribute) {
		if($type(element) != "element") {
			return false;
		}
		if(!$chk(attribute)) { attribute = "rel"; }
		
		var string = element.get(attribute);
		
		if(string === null) {
			return;
		}
		
		this.settings = $merge(this.settings, JSON.decode(string));
	}.protect(),
	
	/**
	 * onDestroy callback.
	 * Called just before the object is destroyed.
	 *
	 * @override
	 * @access protected
	 * @return void
	*/
	onDestroy: function () {}.protect(),
	
	/**
	 * destroy function.
	 * Destroys the object and its resident elements etc..
	 *
	 * @access public
	 * @return void
	*/
	destroy: function () {
		this.onDestroy();
		this.elements().destroy()
		delete this.__elements;
		
		this.__components.each(function(component) {
			if($chk(component.destroy) && $type(component.destroy) == "function") {
				component.destroy();
			}
		});
		delete this.__components;
	},
	
	/**
	 * toElement function.
	 * Returns the root element when $() is called on the UIObject.
	 *
	 * @access public
	 * @return HTMLElement element
	*/
	toElement: function () {
		if($chk(this.element("root"))) {
			return this.element("root");
		} else {
			return null;
		}
	}
});